遇到的问题
前两天在 Andorid 上与使用自签名证书的服务器进行 https 网络通信遇到了问题,主要的问题出在于服务器端的证书不受客户端信任与认证,服务器端也不认识客户端,双方互不认识(在浏览器好歹也会提示用户添加安全证书)。
问题分析
根据需求分析,Android 平台上需要进行双向验证才能够进行正常的 https 通信。而在之前的代码结构中,由于不熟悉 https 沟通方式,错误使用了服务端证书来进行身份识别与验证。
解决方式
进行后续操作的调整,在 centOS 平台上使用 keytool 分别了生成了服务器端证书以及客户端证书,并将客户端证书放置 Android 上,用于连接服务器端时对服务器端的证书进行鉴别与认证。
具体操作如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 1、生成服务器证书库
keytool -validity 365 -genkey -v -alias server -keyalg RSA -keystore server.keystore -dname "CN=commonName,OU=organizationalUnit,O=Organization,L=Locality,ST=state,c=country" -storepass 123456 -keypass 123456 -keysize 2048
2、生成客户端证书库
keytool -validity 365 -genkey -v -alias client -keyalg RSA -storetype PKCS12 -keystore client.p12 -dname "CN=client,OU=organizationalUnit,O=Organization,L=Organization,ST=state,c=country" -storepass 123456 -keypass 123456 -keysize 2048
3、从客户端证书库中导出客户端证书
keytool -export -v -alias client -keystore client.p12 -storetype PKCS12 -storepass 123456 -rfc -file client.cer
4、从服务器证书库中导出服务器证书
keytool -export -v -alias server -keystore server.keystore -storepass 123456 -rfc -file server.cer
5、生成客户端信任证书库(由服务端证书生成的证书库)
keytool -import -v -alias server -file server.cer -keystore client.truststore -storepass 123456 -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
6、将客户端证书导入到服务器证书库(使得服务器信任客户端证书)
keytool -import -v -alias client -file client.cer -keystore server.keystore -storepass 123456
|
keytool 常用参数说明:
参数 |
用途 |
-genkey |
在用户主目录中创建一个默认文件“.keystore” |
-validity |
指定创建的证书有效期为多少天 |
-alias |
产生别名,每个 keystore 都关联一个独一无二的 alias |
-keystore |
指定密钥库的名称 |
-keyalg |
指定密钥的算法 |
-keysize |
指定密钥的长度 |
-dname |
指定证书发行者信息,其中: “CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名 称,ST=州或省份名称,C=单位的两字母国家代码” |
-storepass |
指定密钥库的密码(.keystore密码) |
-keypass |
指定别名条目的密码(私钥密码) |
-storetype |
指定密钥库的存储类型 |
-export |
将 alias 指定的证书导出到文件 |
-import |
将已签名的证书导入密钥库中 |
-rfc |
以Base64的编码格式打印证书 |
-file |
指定导出到文件的文件名 |
-v |
查看密钥库中的证书详细信息 |
在代码结构上,在 http 通信代码中添加 SSL 通信机制,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public static final String KEY_STORE_TYPE_BKS = "BKS"; public static final String KEY_STORE_TYPE_P12 = "PKCS12"; public static final String KEY_STORE_CLIENT_PATH = "swaypay.p12"; public static final String KEY_STORE_TRUST_PATH = "swaypay.truststore"; public static final String KEY_STORE_PASSWORD = "superssl1"; public static final String KEY_STORE_TRUST_PASSWORD = "superssl1"; public static final String KEY_STORE_TYPE_X509 = "X509"; public static final String SSL_CONTEXT_PROTOCOL = "TLS";
public void setSSLConnection() { KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12); KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS);
InputStream ksIn = ContextHolder.getContext().getAssets().open(KEY_STORE_CLIENT_PATH); InputStream tsIn = ContextHolder.getContext().getAssets().open(KEY_STORE_TRUST_PATH); keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray()); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KEY_STORE_TYPE_X509); keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray()); trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray()); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(trustStore); sslContext = SSLContext.getInstance(SSL_CONTEXT_PROTOCOL); sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); }
... private HttpURLConnection createHttpURLConnection(String targetUrl) { ... if (httpURLConnection instanceof HttpsURLConnection) { setSSLConnection(); ((HttpsURLConnection) httpURLConnection).setSSLSocketFactory(sslContext.getSocketFactory()); ((HttpsURLConnection) httpURLConnection).setHostnameVerifier((String hostname, SSLSession session) -> { return true; }); } ... }
|
参考资料:
[1] http://frank-zhu.github.io/android/2014/12/26/android-https-ssl/